﻿using System;
using System.Text;

namespace HCore
{
    public class OutputMemeryBitStream
    {
        private byte[] _buffer;
        private int _bitHead;
        private int _bitCapacity;

        private void ReallocBuffer(int inNewBitCapacity)
        {
            if (_buffer == null)
            {
                _buffer = new byte[inNewBitCapacity >> 3];
            }
            else
            {
                byte[] newBuffer = new byte[inNewBitCapacity >> 3];
                _buffer.CopyTo(newBuffer, 0);
                _buffer = newBuffer;
            }

            _bitCapacity = inNewBitCapacity;
        }

        public OutputMemeryBitStream()
        {
            _bitHead = 0;
            ReallocBuffer(1024 * 8);
        }

        public void WriteBits(byte inData, int inBitCount)
        {
            int nextBitHead = _bitHead + inBitCount;

            if (nextBitHead > _bitCapacity)
            {
                ReallocBuffer(Math.Max(_bitCapacity * 2, nextBitHead));
            }

            int byteOffset = _bitHead >> 3;
            int bitOffset = _bitHead & 0x7;

            byte currentMask = (byte) ~(0xff << bitOffset);
            _buffer[byteOffset] = (byte) ((_buffer[byteOffset] & currentMask) | (inData << bitOffset));

            int bitsFreeThisByte = 8 - bitOffset;

            if (bitsFreeThisByte < inBitCount)
            {
                _buffer[byteOffset + 1] = (byte) (inData >> bitsFreeThisByte);
            }

            _bitHead = nextBitHead;
        }

        public void WriteBits(byte[] inData, int inBitCount)
        {
            int srcByte = 0;
            while (inBitCount > 8)
            {
                WriteBits(inData[srcByte], 8);
                srcByte++;
                inBitCount -= 8;
            }

            if (inBitCount > 0)
            {
                WriteBits(inData[srcByte], inBitCount);
            }
        }

        public void WriteBytes(byte[] inData, int inByteCount)
        {
            WriteBits(inData, inByteCount << 3);
        }

        public void WriteBool(bool inData)
        {
            byte b = (byte) (inData ? 1 : 0);
            WriteBits(b, 1);
        }

        public void WriteByte(byte inData)
        {
            WriteBits(inData, 8);
        }

        public void WriteShort(short inData)
        {
            byte b = (byte) inData;
            WriteByte(b);
            b = (byte) (inData >> 8);
            WriteByte(b);
        }

        public void WriteInt(int inData)
        {
            byte b = (byte) inData;
            WriteByte(b);
            b = (byte) (inData >> 8);
            WriteByte(b);
            b = (byte) (inData >> 16);
            WriteByte(b);
            b = (byte) (inData >> 24);
            WriteByte(b);
        }

        public void WriteUInt(uint inData)
        {
            WriteInt((int) inData);
        }

        public void WriteFix(Fix inData)
        {
            WriteInt(inData.Raw);
        }

        public void WriteString(string inData)
        {
            byte[] bytes = Encoding.UTF8.GetBytes(inData);
            WriteShort((short)bytes.Length);
            WriteBytes(bytes, bytes.Length);
        }

        public int GetBitLength()
        {
            return _bitHead;
        }

        public int GetByteLength()
        {
            return (_bitHead + 7) >> 3;
        }

        public byte[] GetBuffer()
        {
            return _buffer;
        }
    }

    public class InputMemoryBitStream
    {
        private byte[] _buffer;
        private int _bitHead;
        private int _bitCapacity;

        public InputMemoryBitStream(byte[] buffer, int inByteCount)
        {
            _buffer = buffer;
            _bitCapacity = inByteCount * 8;
            _bitHead = 0;
        }

        public int GetByteLength()
        {
            return (_bitCapacity + 7) >> 3;
        }

        public byte[] GetBuffer()
        {
            return _buffer;
        }

        public int GetBitHead()
        {
            return _bitHead;
        }

        public void SetBitHead(int bitHead)
        {
            _bitHead = bitHead;
        }

        public byte ReadBits(int inBitCount)
        {
            int byteOffset = _bitHead >> 3;
            int bitOffset = _bitHead & 0x7;

            byte outData = (byte) (_buffer[byteOffset] >> bitOffset);
            int bitsFreeThisByte = 8 - bitOffset;
            if (bitsFreeThisByte < inBitCount)
            {
                outData |= (byte) (_buffer[byteOffset + 1] << bitsFreeThisByte);
            }

            _bitHead += inBitCount;

            outData &= (byte) ~(0x00ff << inBitCount);

            return outData;
        }

        public byte[] ReadLBits(int inBitCount)
        {
            byte[] destBytes = new byte[(inBitCount + 7) >> 3];
            //write all the bytes
            int destByte = 0;
            while (inBitCount > 8)
            {
                destBytes[destByte] = ReadBits(8);
                ++destByte;
                inBitCount -= 8;
            }

            //write anything left
            if (inBitCount > 0)
            {
                destBytes[destByte] = ReadBits(inBitCount);
            }

            return destBytes;
        }

        public bool ReadBool()
        {
            byte b = ReadBits(1);
            return b > 0;
        }

        public byte ReadByte()
        {
            return ReadBits(8);
        }

        public short ReadShort()
        {
            byte a = ReadBits(8);
            byte b = ReadBits(8);

            return (short) (a | b << 8);
        }

        public int ReadInt()
        {
            byte a = ReadBits(8);
            byte b = ReadBits(8);
            byte c = ReadBits(8);
            byte d = ReadBits(8);

            return a | b << 8 | c << 16 | d << 24;
        }

        public uint ReadUint()
        {
            return (uint) ReadInt();
        }

        public byte[] ReadBytes(int inByteCount)
        {
            return ReadLBits(inByteCount << 3);
        }

        public Fix ReadFix()
        {
            int raw = ReadInt();
            return new Fix(raw);
        }

        public string ReadString()
        {
            short len = ReadShort();
            byte[] bytes = ReadBytes(len);
            return Encoding.UTF8.GetString(bytes);
        }
    }
}